<?php
class S3Utils {
	// Consts for sorting
	const SORT_ASC = 1;
	const SORT_DSC = 2;
	const SORT_BUCKET_NAME = 4;
	const SORT_BUCKET_CREATE_DATE = 8;
	
	// Consts for file types
	const FILE_TYPE_FILE = 1;
	const FILE_TYPE_DIR = 2;
	
	const FILE_ACL_PRIVATE = 1;
	const FILE_ACL_PUBLIC = 2;
	const FILE_ACL_OPEN = 4;
	const FILE_ACL_AUTH_READ = 8;
	const FILE_ACL_OWNER_READ = 16;
	const FILE_ACL_OWNER_FULL_CONTROL = 32;
	
	private $s3 = false;
	private $httpRoot = null;
	private $fileList = null;
	private $sdb;
	private $sdbDomain;
	public $progress = true;
	private $simpleProgress = array(
		0 => "|",
		1 => "/",
		2 => "-");
	private $simpleProgressIndex = 0;
	private $temp;
	private $progressBar;
	
	public $s3Bucket = null;
	public $s3Prefix = null;
	
	public $aclList = array(
		self::FILE_ACL_PRIVATE => "Private",
		self::FILE_ACL_PUBLIC => "Public",
		self::FILE_ACL_OPEN => "Open",
		self::FILE_ACL_AUTH_READ => "Auth Read",
		self::FILE_ACL_OWNER_READ => "Owner Read",
		self::FILE_ACL_OWNER_FULL_CONTROL => "Owner Full Control"
	);
		
	function __construct($s3Bucket = null, $s3Prefix = null) {
		// Instantize a new S3 object
		$this->s3 = new AmazonS3();

		// throw exception on error
		if (!$this->s3) {
			throw new Exception(Errors::S3Utils_MESG_S3_FAILED_INIT_S3, Errors::S3Utils_CODE_S3_FAILED_INIT_S3);
		}
		
		// Set the bucket, if provided... also set the httpRoot
		if (!is_null($s3Bucket)) {
			$this->s3Bucket = $s3Bucket;
			$this->httpRoot = "http://" . $this->s3Bucket . AmazonS3::DEFAULT_URL;
		}
		
		// Set the prefix, if provided
		if (!is_null($s3Prefix)) {
			$this->s3Prefix = $s3Prefix;
		}
		$this->progress(true);
	}
	
	private function getSet($var, $value) {
		if (!is_null($value)) {
			$this->$var = $value;
		}
		return $this->$var;
	}
	
	public function progress($value) {
		$val = $this->getSet("progress", $value);
		if ($val === true) {
			$this->s3->register_streaming_read_callback(array($this, "__readCallback"));
			$this->s3->register_streaming_write_callback(array($this, "__writeCallback"));
		} else {
			$this->s3->register_streaming_read_callback('');
			$this->s3->register_streaming_write_callback('');
		}
		return $val;
	}
	
	// read = upload to S3
	public function __readCallback ($curlHandle, $fileHandle, $length) {
		$curlInfo = curl_getinfo($curlHandle);
		$this->bar->update($curlInfo["size_upload"]);
		/*
		$sizeUpload = $curlInfo["size_upload"]; //Total number of bytes uploaded
		$speedUpload = round($curlInfo["speed_upload"] / 1024, 2); //Average upload speed in KBps
		$sizeUpload = $curlInfo["size_upload"]; //Total number of bytes uploaded
		$contentSize = $curlInfo["upload_content_length"]; //Specified size of upload
		$percentage = round(($sizeUpload / $contentSize) * 100, 2);
		$secondsRemain = round(($contentSize - $sizeUpload) / $speedUpload, 2);   
		if ($this->temp["uploadCount"] == 40) {
			printf("\tUpload speed: %01.2f KBps | %01.2f%% complete | %01.2f secs left\n", $speedUpload, $percentage, $secondsRemain);
			$this->temp["uploadCount"] = 0;
		}
		*/
		/*		
		if (($percentage % 10) == 0) {
			if (isset($this->temp["lastPct"]) && $this->temp["lastPct"] != $percentage) {
				echo "$percentage%";
			}
			$this->temp["lastPct"] = $percentage;
		} elseif (($percentage % 2) == 0) {
			echo ".";
		}
		*/
	}
	
	// write = download from S3
	public function __writeCallback ($curlHandle, $length) {
		$curlInfo = curl_getinfo($curlHandle);
		$percentage = floor(($curlInfo["size_download"] / $curlInfo["download_content_length"]) * 100);
		if (($percentage % 10) == 0) {
			if (isset($this->temp["lastPct"]) && $this->temp["lastPct"] != $percentage) {
				echo "$percentage%";
			}
			$this->temp["lastPct"] = $percentage;
		} elseif (($percentage % 8) == 0) {
			echo ",";
		}
	}
	
	public function sdbDomain($value) {
		return $this->getSet("sdbDomain", $value);
	}
	
	public function syncToSDB($value) {
		return $this->getSet("syncToSDB", $value);
	}
	
	public function bucketList() {
		$res = $this->s3->list_buckets();
		if (!$res->isOK()) {
			throw new Exception(Errors::S3Utils_MESG_S3_FAILED_BUCKET_LIST . " (" . $res->body->Message . ")", Errors::S3Utils_CODE_S3_FAILED_BUCKET_LIST);
		}
		
		$i = 0;
		foreach ($res->body->Buckets->Bucket as $bucket) {
			$bucketList[] = array("id" => $i++, "name" => (string) $bucket->Name, "createDate" => strtotime((string) $bucket->CreationDate));
		}
		return (isset($bucketList)) ? $bucketList : null;
	}
	
	public function getBucketACL() {
		$res = $this->s3->get_bucket_acl($this->s3Bucket);
		if (!$res->isOK()) {
			throw new Exception(Errors::S3Utils_MESG_S3_FAILED_BUCKET_ACL_LIST . " (" . $res->body->Message . ")", Errors::S3Utils_CODE_S3_FAILED_BUCKET_ACL_LIST);
		}
		$acl = array(
			"owner" => array("id" => (string) $res->body->Owner->ID, "DisplayName" => (string) $res->body->Owner->DisplayName),
			"acl" => $res->body->AccessControlList); 
		return $acl;
	}

	public function httpRoot($value = null) {
		if (!is_null($value)) {
			if (is_null($this->s3Bucket)) {
				throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
			}
			$this->httpRoot = "$http://" . $this->s3Bucket . AmazonS3::DEFAULT_URL;
		}
		return $this->httpRoot;
	}
	
	public function bucketGetObjects($usePrefix = true, $localPrefix = false, $skipacl = false) {
		$this->temp = null;
		$this->__bucketGetObjects($usePrefix, $localPrefix, $skipacl);
		return is_array($this->temp) ? $this->temp : false;
		
	}
	
	public function __bucketGetObjects($usePrefix = true, $localPrefix = false, $skipacl = false, $marker = false) {
		// Sanity Checks
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		// Get a list of objects from AWS S3
		$opts = array();
		if ($usePrefix) {
			if (!$localPrefix) {
				$localPrefix = $this->s3Prefix;
			}
			$opts["delimiter"] = "/";
			$opts["prefix"] = $localPrefix;
		}
		
		if ($marker !== false) {
			$opts["key-marker"] = $marker;
			echo "Marker is $marker\n";
		}		
		
		$res = $this->s3->list_bucket_object_versions($this->s3Bucket, $opts);
		
		$objectList = array();

		// Process current versions
		if ($res->isOK()) {
			foreach ($res->body->Version as $object) {
				if ($skipacl === false) {
					$getACL = $this->s3->get_object_acl($this->s3Bucket, $object->Key, array("versionId" => $object->VersionId));
					$acl = self::FILE_ACL_PRIVATE;
					if ($getACL->isOK()) {
						foreach($getACL->body->AccessControlList->Grant as $grant) {
							if (isset($grant->Grantee->URI)) {
								if ((string) $grant->Grantee->URI == AmazonS3::USERS_ALL) {
									if ((string) $grant->Permission == AmazonS3::GRANT_READ) {
										$acl = self::FILE_ACL_PUBLIC;
									}
								}
							}
						}
					} else {
						var_dump($getACL);
						return false;
					}
				} else {
					$acl = null;
				}

				$name = str_replace($this->s3Prefix, "", (string) $object->Key);
				$objectList[$name][(string) $object->VersionId] = array(
					"name" => $name,
					"versionID" => (string) $object->VersionId,
					"isLatest" => ((string) $object->IsLatest == "true") ? true : false,
					"type" => self::FILE_TYPE_FILE,
					"acl" => $acl,
					"LastModified" => strtotime((string) $object->LastModified),
					"md5" => trim(str_replace('"', "", (string) $object->ETag)),
					"size" => (int) $object->Size,
					"storage" => ((string) $object->StorageClass == "REDUCED_REDUNDANCY") ? AmazonS3::STORAGE_REDUCED : AmazonS3::STORAGE_STANDARD,
					"owner" => array("id" => (string) $object->Owner->ID, "displayName" => (string) $object->Owner->DisplayName),
					"foundIn" => "Version"
				);
			}

			// Process deleted versions
			$acl = null;
			foreach ($res->body->DeleteMarker as $object) {
				$name = str_replace($this->s3Prefix, "", (string) $object->Key);
				$objectList[$name][(string) $object->VersionId] = array(
					"name" => $name,
					"versionID" => (string) $object->VersionId,
					"isLatest" => ((string) $object->IsLatest == "true") ? true : false,
					"type" => self::FILE_TYPE_FILE,
					"acl" => $acl,
					"LastModified" => strtotime((string) $object->LastModified),
					"owner" => array("id" => (string) $object->Owner->ID, "displayName" => (string) $object->Owner->DisplayName),
					"foundIn" => "DeleteMarker"
				);
				
			}
			foreach ($res->body->CommonPrefixes as $commonPrefix) {
				$name = str_replace($this->s3Prefix, "", (string) $commonPrefix->Prefix);
				if ($name == "/") { continue; }
				$objectList[$name] = array("name" => $name, "type" => self::FILE_TYPE_DIR);
			}
		} else {
			$code = $res->body->Code;
			$message = $res->body->Message;
			throw new Exception (Errors::S3Utils_MESG_S3_FAILED_TO_GET_OBJECTS . ": ($code | $message)", Errors::S3Utils_CODE_S3_FAILED_TO_GET_OBJECTS);
		}

		if (!is_array($this->temp)) {
			$this->temp = array();
		}
		$this->temp = array_merge($this->temp, $objectList);
		if (isset($res->body->IsTruncated) && ((string) $res->body->IsTruncated == "true")) {
			$nextMarker = (string) $res->body->NextKeyMarker;	
			$this->__bucketGetObjects($usePrefix, $localPrefix, $skipacl, $nextMarker);
		} else {
			return true;
		}
	}

	public function bucketExist() {
		return $this->s3->if_bucket_exists($this->s3Bucket);
	}
	
	public function bucketCreate($acl = AmazonS3::ACL_PRIVATE, $region = AmazonS3::REGION_US_E1) {
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		if ($this->bucketExist()) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_ALREADY_EXIST, Errors::S3Utils_CODE_S3_BUCKET_ALREADY_EXIST);
		}
		
		$res = $this->s3->create_bucket($this->s3Bucket, $region, $acl);
		if (!$res->isOK()) {
			$code = $res->body->Code;
			$message = $res->body->Message;
			throw new Exception (Errors::S3Utils_MESG_S3_FAILED_CREATE_BUCKET . ": ($code | $message)", Errors::S3Utils_CODE_S3_FAILED_CREATE_BUCKET);
		}		
		return true;
	}
	
	public function bucketSize() {
		$res = $this->s3->get_bucket_filesize($this->s3Bucket);
		return (int) $res;
	}
	
	public function bucketObjectCount() {
		$res = $this->s3->get_bucket_object_count($this->s3Bucket);
		return (int) $res;
	}
	
	public function bucketDelete($force) {
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		if (!$this->bucketExist()) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_DOESNT_EXIST, Errors::S3Utils_CODE_S3_BUCKET_DOESNT_EXIST);
		}

		$res = $this->s3->delete_all_object_versions($this->s3Bucket);
		if ($res === true) {
			$res = $this->s3->delete_bucket($this->s3Bucket, $force);
			if ($res === false) {
				throw new Exception (Errors::S3Utils_MESG_S3_BUCKET_FAILED_DELETE, Errors::S3Utils_CODE_S3_BUCKET_FAILED_DELETE);
			}
		} else {
			throw new Exception (Errors::S3Utils_MESG_S3_BUCKET_FAILED_DELETE_ALL_OBJ_VERSIONS, Errors::S3Utils_CODE_S3_BUCKET_FAILED_DELETE_ALL_OBJ_VERSIONS);
		}
		return true;
	}

	public function bucketIsVersioningEnabled() {
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		$res = $this->s3->get_versioning_status($this->s3Bucket);
		if ($res->isOK()) {
			if (isset($res->body->Status) && ((string) $res->body->Status == "Enabled")) {
				return true;
			} else {
				return false;
			}
		} else {
			$code = $res->body->Code;
			$message = $res->body->Message;
			throw new Exception (Errors::S3Utils_MESG_S3_BUCKET_FAILED_FETCH_VERSIONING . ": ($code | $message)", Errors::S3Utils_CODE_S3_BUCKET_FAILED_FETCH_VERSIONING);
		}
	}
	
	public function bucketEnableVersioning() {
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		$res = $this->s3->enable_versioning($this->s3Bucket);
		if ($res->isOK()) {
			return true;
		} else {
			$code = $res->body->Code;
			$message = $res->body->Message;
			throw new Exception (Errors::S3Utils_MESG_S3_BUCKET_FAILED_ENABLE_VERSIONING . ": ($code | $message)", Errors::S3Utils_CODE_S3_BUCKET_FAILED_ENABLE_VERSIONING);
		}
	}
	
	public function bucketDisableVersioning() {
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		$res = $this->s3->disable_versioning($this->s3Bucket);
		if ($res->isOK()) {
			return true;
		} else {
			$code = $res->body->Code;
			$message = $res->body->Message;
			throw new Exception (Errors::S3Utils_MESG_S3_BUCKET_FAILED_DISABLE_VERSIONING . ": ($code | $message)", Errors::S3Utils_CODE_S3_BUCKET_FAILED_DISABLE_VERSIONING);
		}
	}
	
	private function buildRecursiveFileList($rootDir) {
		if (is_dir($rootDir)) {
			$d = dir($rootDir);
			while (false !== ($entry = $d->read())) {
				if ($entry == "." || $entry == "..") { continue; }
				$fqpn = "$rootDir/$entry";
				if (is_dir($fqpn)) {
					$this->buildRecursiveFileList($fqpn);
				} else {
					$this->fileList[] = $fqpn;
				}
			}
			$d->close();
		}
	}

	public function objectUpload($sdbDomain, $localObject, $remoteName = null, $recurse = false, $acl = AmazonS3::ACL_PRIVATE, $storage = AmazonS3::STORAGE_REDUCED) {
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		if ($recurse) {
			$this->buildRecursiveFileList($localObject);
		} else {
			$this->fileList[] = $localObject;
		}
		
		$fileList = $this->fileList;
		$this->fileList = null;

		$sdb = new SDBUtils();

		$items = array();
		foreach ($fileList as $file) {
			if (is_null($remoteName)) {
				$remoteObject = $this->s3Prefix . $file;
			} else {
				$remoteObject = $remoteName;
			}
			if ($this->objectIsUpToDate($sdbDomain, $file, $remoteObject)) {
				echo "$file is current- no need to upload\n"; 
				continue; 
			}
			echo "\nuploading $file\n";
			$this->bar = new Console_ProgressBar('- %fraction% [%bar%] %percent% ETA: %estimate%', '=>', '-', 70, filesize($file));
			$res = $this->s3->create_object($this->s3Bucket, $remoteObject, array("fileUpload" => $file, "acl" => $acl, "storage" => $storage));
			if (!$res->isOK()) {
				$code = $res->body->Code;
				$message = $res->body->Message;
				throw new Exception (Errors::S3Utils_MESG_S3_OBJECT_UPLOAD_FAILED . ": ($code | $message)", Errors::S3Utils_CODE_S3_OBJECT_UPLOAD_FAILED);
			}
			$latestVersion = $this->objectGetLatest($remoteObject);
			$lastestVersionID = $latestVersion["versionID"];
			 
			$keypairs = array(
				"name" => $remoteObject,
				"type" => $latestVersion["type"],
				"LastModified" => $latestVersion["LastModified"],
				"md5" => $latestVersion["md5"],
				"size" => $latestVersion["size"],
				"storage" => $latestVersion["storage"],
				"owner" => json_encode($latestVersion["owner"])
			);
			$sdb->itemPut("$sdbDomain-masterList", $remoteObject, array("latestVersionID" => $lastestVersionID), true);
			$sdb->itemPut("$sdbDomain-versions", $lastestVersionID, $keypairs, true);
		}
		return true;
	}
	
	public function objectIsUpToDate ($sdbDomain, $localObject, $remoteObject) {
		$sdb = new SDBUtils();
		
		$localMD5 = md5_file($localObject);
		
		// fetch the item record to get the latest version
		if (false !== ($item = $sdb->itemGet("$sdbDomain-masterList", $remoteObject))) {
			if (isset($item["latestVersionID"])) {
				$latestVersionID = $item["latestVersionID"];
				if (false !== ($version = $sdb->itemGet("$sdbDomain-versions", $latestVersionID))) {
					if ($localMD5 == $version["md5"]) {
						// the remote object is up-to-date
						return true;
					} else {
						return false;
					}
				} else {
					// version look up was good, but item structure was bad...
					// So, no: the object is not up of date
					return false;
				}
			} else {
				// look up was good, but item structure was bad...
				// So, no: the object is not up of date
				return false;
			}
		} else {
			// no, the object is no "up of date"
			return false;
		}
	}
	
	private function objectNuke ($sdbDomain, $remoteObject) {
		$pcreObj = preg_quote($remoteObject, "/");
		$res = $this->s3->delete_all_object_versions($this->s3Bucket, "/$pcreObj/");
		if ($res === false) {
			throw new Exception(Errors::S3Utils_MESG_S3_OJB_NUKE_FAIL, Errors::S3Utils_CODE_S3_OJB_NUKE_FAIL);
		}
		$sdb = new SDBUtils();
		$items = $sdb->domainSelect("SELECT * FROM `$sdbDomain-versions` WHERE name = '$remoteObject'");
		if (is_array($items)) {
			foreach ($items as $versionID => $version) {
				$sdb->itemDelete("$sdbDomain-versions", $versionID);
			}
		}
		$sdb->itemDelete("$sdbDomain-masterList", $remoteObject);
		
		return true;
	}
	
	public function objectDelete($sdbDomain, $remoteObject, $remoteObjectVersionID) {
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		if ($remoteObjectVersionID == "*") {
			return $this->objectNuke($sdbDomain, $remoteObject);
		}
		echo "\tDeleting S3... ";
		$res = $this->s3->delete_object($this->s3Bucket, $remoteObject, array("versionId" => $remoteObjectVersionID));
		echo "Deleted\n";
		
		if (!$res->isOK()) {
			$code = $res->body->Code;
			$message = $res->body->Message;
			throw new Exception (Errors::S3Utils_MESG_S3_OBJECT_DELETE . ": ($code | $message)", Errors::S3Utils_CODE_S3_OBJECT_DELETE);
		}

		$sdb = new SDBUtils();
		
		// delete the version
		$sdb->itemDelete("$sdbDomain-versions", $remoteObjectVersionID);
		$items = $sdb->domainSelect("SELECT * FROM `$sdbDomain-versions` WHERE name = '$remoteObject'");
		if (is_null($items)) {
			// if there's no more versions in the dbase, delete the object entirely
			$sdb->itemDelete("$sdbDomain-masterList", $remoteObject);
		}
		return true;
	}
	
	public function objectGetLatest($object) {
		// Sanity Checks
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		foreach ($this->bucketGetObjects(true, $object) as $objects) {
			foreach ($objects as $version) {
				if ($version["isLatest"]) {
					return $version;
				}
			}
		}
		return false;
	}
	
	public function objectGetUrl($remoteObject, $versionID = null, $time = null) {
		// Sanity Checks
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		$url = $this->s3->get_object_url($this->s3Bucket, $remoteObject, $time, array("versionId" => $versionID));

		// return the Url	
		if ($url) {
			return $url;
		}
		throw new Exception(Errors::S3Utils_MESG_OBJECT_GET_URL_FAIL, Errors::S3Utils_CODE_OBJECT_GET_URL_FAIL);
	}
	
	public function objectGetACL($remoteObject, $versionID = null) {
		$res = $this->s3->get_object_acl($this->s3Bucket, $remoteObject, array("versionId" => $versionID));
		if (!$res->isOK()) {
			$code = $res->body->Code;
			$message = $res->body->Message;
			throw new Exception (Errors::S3Utils_MESG_S3_OBJECT_GET_ACL_FAIL . ": ($code | $message)", Errors::S3Utils_CODE_S3_OBJECT_GET_ACL_FAIL);
		}
			
		var_dump($res->body); 
	}
	
	public function objectDownload($remoteObject, $localFile, $versionID = null) {
		// Sanity Checks
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		if (!is_dir(dirname($localFile))) {
			if (mkdir (dirname($localFile), 0775, true) === false) {
				throw new Exception(Errors::S3Utils_MESG_CANT_MK_LOCAL_DIR . " (" . dirname($localFile) . ")", Errors::S3Utils_CODE_CANT_MK_LOCAL_DIR);
			}
		}
		$fh = fopen($localFile, "w+");
		if ($fh === false) {
			throw new Exception(Errors::S3Utils_MESG_CANT_OPEN_LOCAL_FILE . "($localFile)", Errors::S3Utils_CODE_CANT_OPEN_LOCAL_FILE);
		}
		
		$res = $this->s3->get_object($this->s3Bucket, $remoteObject, array("fileDownload" => $fh)); //, "versionId" => $versionID));
		fclose($fh);

		if ($res->isOK(404)) {
			unlink ($localFile);
			throw new Exception (Errors::S3Utils_MESG_S3_OBJECT_DOWNLOAD_FAIL . ": (404 | 404 messages received from S3)", Errors::S3Utils_CODE_S3_OBJECT_DOWNLOAD_FAIL);
		} elseif (!$res->isOK()) {
			unlink ($localFile);
			$code = $res->body->Code;
			$message = $res->body->Message;
			throw new Exception (Errors::S3Utils_MESG_S3_OBJECT_DOWNLOAD_FAIL . ": ($code | $message)", Errors::S3Utils_CODE_S3_OBJECT_DOWNLOAD_FAIL);
		}
		return true;
	}
	
	public function objectDownloadStream($remoteObject, $versionID = null) {
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		$res = $this->s3->get_object($this->s3Bucket, $remoteObject);
		if ($res->isOK()) {
			return $res->body;
		} elseif (!$res->isOK()) {
			$code = $res->body->Code;
			$message = $res->body->Message;
			throw new Exception (Errors::S3Utils_MESG_S3_OBJECT_DOWNLOAD_FAIL . ": ($code | $message)", Errors::S3Utils_CODE_S3_OBJECT_DOWNLOAD_FAIL);
		}
	}
	
	public function objectUploadStream($sdbdomain, $data, $remoteObject, $versionID = null, $acl = AmazonS3::ACL_PRIVATE, $storage = AmazonS3::STORAGE_REDUCED) {
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		$sdb = new SDBUtils();
		
		$res = $this->s3->create_object($this->s3Bucket, $remoteObject, array("body" => $data, "acl" => $acl, "storage" => $storage));
		if (!$res->isOK()) {
			$code = $res->body->Code;
			$message = $res->body->Message;
			throw new Exception (Errors::S3Utils_MESG_S3_OBJECT_UPLOAD_FAILED . ": ($code | $message)", Errors::S3Utils_CODE_S3_OBJECT_UPLOAD_FAILED);
		}
		$latestVersion = $this->objectGetLatest($remoteObject);
		$lastestVersionID = $latestVersion["versionID"];
		 
		$keypairs = array(
			"name" => $remoteObject,
			"type" => $latestVersion["type"],
			"LastModified" => $latestVersion["LastModified"],
			"md5" => $latestVersion["md5"],
			"size" => $latestVersion["size"],
			"storage" => $latestVersion["storage"],
			"owner" => json_encode($latestVersion["owner"])
		);
		$sdb->itemPut("$sdbdomain-masterList", $remoteObject, array("latestVersionID" => $lastestVersionID), true);
		$sdb->itemPut("$sdbdomain-versions", $lastestVersionID, $keypairs, true);
	}
	
	public function objectSetStorage($remoteObject, $storageMode) {
		// Sanity Checks
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		
		$res = $this->s3->change_storage_redundancy($this->s3Bucket, $remoteObject, $storageMode);
		if (!$res->isOK()) {
			$code = $res->body->Code;
			$message = $res->body->Message;
			throw new Exception (Errors::S3Utils_MESG_S3_OBJECT_STORAGE_CHANGE . ": ($code | $message)", Errors::S3Utils_CODE_S3_OBJECT_STORAGE_CHANGE);
		}
		return true;
	}
	
	public function slowObjectAllNuke($marker = false) {
		//public function __bucketGetObjects($usePrefix = true, $localPrefix = false, $skipacl = false, $marker = false) {
		// Sanity Checks
		if (is_null($this->s3Bucket)) {
			throw new Exception(Errors::S3Utils_MESG_S3_BUCKET_NOT_SET, Errors::S3Utils_CODE_S3_BUCKET_NOT_SET);
		}
		echo "About to delete everything...\n";
		
		$opts = array();
			
		if ($marker !== false) {
			$opts["key-marker"] = $marker;
			echo "Marker is $marker\n";
		}		
		
		$res = $this->s3->list_bucket_object_versions($this->s3Bucket, $opts);
		$objectList = array();
		// Process the file list
		if ($res->isOK()) {
			//var_dump($res);
			foreach ($res->body->Version as $object) {
				$name = str_replace($this->s3Prefix, "", (string) $object->Key);
				$versionID = (string) $object->VersionId;
				echo "Deleting $name, version ID $versionID: ";
				$deleteRes = $this->s3->delete_object($this->s3Bucket, $name, array("versionId" => $versionID));
				if ($deleteRes->isOK()) {
					echo "deleted\n";
				} else {
					echo "FAILED!\n";
					var_dump($deleteRes);
					return false;
				}
			}
			foreach ($res->body->DeleteMarker as $object) {
				$name = str_replace($this->s3Prefix, "", (string) $object->Key);
				echo "Deleting (deleted) $name: ";
				$deleteRes = $this->s3->delete_object($this->s3Bucket, $name);
				if ($deleteRes->isOK()) {
					echo "deleted\n";
				} else {
					echo "FAILED!\n";
					var_dump($deleteRes);
					return false;
				}
			}
		} else {
			$code = $res->body->Code;
			$message = $res->body->Message;
			throw new Exception (Errors::S3Utils_MESG_S3_FAILED_TO_GET_OBJECTS . ": ($code | $message)", Errors::S3Utils_CODE_S3_FAILED_TO_GET_OBJECTS);
		}

		if (isset($res->body->IsTruncated) && ((string) $res->body->IsTruncated == "true")) {
			$nextMarker = (string) $res->body->NextKeyMarker;	
			$this->__bucketGetObjects($nextMarker);
		}
	}
}

?>